/*TODO*/
/*

who?|done?| function name:
ela	| yes |	int mulMatrix(Mat mat1, Mat mat2, Mat matRes, int N_Off, int M_Off, int O_Off, int nSize, int mSize, int oSize);
ela	| yes |	void parser(Mat matA, Mat matB, int N, int M, int O);
ela	| yes |	void printMatrix (Mat mat, int rows, int cols);

ela	| no  |	void updateMatrix (Job* job, Mat blockToAdd, Mat MatC);
ela	| no  | _threadRunWrapper

jonathan	| no  |	void divideToJobs(int N, int M, int O, int numOfThreads, Queue queue);
jonathan	| no  |	void doJob (Job* job, Mat matA, Mat matB, Mat matC);
jonathan	| no  |	void threadRun (Queue queue, Mat matA, Mat matB, Mat matC) (need to finish)

	
	| no  |  main and allocate function (need to finish)
	| no  |	_freeAll
	| no  |	int getBlockID(Job job);

*/


#include "matmul.h"
#include <assert.h>

void _freeAll(Mat matA, Mat matB, Mat matC, int N, int M, int O, Queue jobQueue);
int _allocAll(Mat matA, Mat matB, Mat matC, int N, int M, int O, Queue jobQueue);
void printOutput(Mat matA, Mat matB, Mat matC, int N, int M, int O);
void scanOneMatrix(Mat mat, int rows, int cols);
void printMatrix (Mat mat, int rows, int cols);
void * _threadRunWrapper(void * threadRunWrapperArgs);
int _roof(float value);
static MutexArray locks = NULL; //TODO: I don't think the initialization here is legal -Ella
Job _dequeueJob(Queue jobQueue);
void _freeAll(Mat matA, Mat matB, Mat matC, int N, int M, int O, Queue jobQueue){

}




/*
 *
 */
int main(char * argv, int argc) {
	assert(argc == 5);

	//a to i argv
	int N,M,O,threadNum;
	N=atoi(argv[1]);
	M=atoi(argv[2]);
	O=atoi(argv[3]);
	threadNum=atoi(argv[4]);
	
	Mat matA = NULL;
	Mat matB = NULL;
	Mat matC = NULL;
	Queue jobQueue = NULL;

	int retVal = _allocAll(matA, matB, matC, N, M, O, jobQueue);
	if (retVal != ALLOCATION_SUCCESSFUL){
		return 1;
	}
	
	
	//call parser
	parser(matA, matB,  N,  M,  O);


	//create queue (with lock)
	jobQueue.size = _roof(N/threadNum)*_roof(M/threadNum)*_roof(O/threadNum); //TODO: implement _roof.
	jobQueue.iterator = 0;
	//TODO: initialize pthread_mutex.

	//call function devideToJobs (fills queue)
	divideToJobs( N,  M,  O, threadNum,  jobQueue);

	//creates threads
	//TODO: allocate thread_t array in allocAll call it threadArray
        pthread_t * threadArray[] = malloc(threadNum * sizeof(pthread_t));
        
	ArgStruct threadRunWrapperArgs;
	threadRunWrapperArgs.queue = jobQueue;
	threadRunWrapperArgs.matA = matA;
	threadRunWrapperArgs.matB = matB;
	threadRunWrapperArgs.matC = matC;
	
	int i = 0;
	for (; i < threadNum ; ++i){
		int res = pthread_create(threadArray+i, NULL, _threadRunWrapper, (void*)(&threadRunWrapperArgs)); //TODO: make sure about pthread_attr_t being NULL
		if (res != 0){
			_freeAll();
			return 1; //TODO: verify returning terminates all threads
		}
	}


	//join threads
	for (i = 0; i < threadNum ; ++i){
		int res = pthread_join(threadArray[i], NULL); //TODO: make sure about pthread_attr_t being NULL
		if (res != 0){
			_freeAll();
			return 1; //TODO: verify returning terminates all threads
		}
	}


	/*each son takes jobs from queue (locking and unlocking) and does it.
		sleeps if queue is already locked, Exits when no more jobs.
		(at end of job updates matC)*/

	
	//call print_output
	print_output(matA, matB, matC, N, M, O);
	return 0;
}

void scanOneMatrix(Mat mat, int rows, int cols) {// TODO: do we need to check input? check if scan fails?
	int i, j;
	for (i=0 ; i<rows ; ++i) {
		for (j=0 ; j<cols ; ++j) {
			scanf("%d", &(mat[i][j]));
		}
	}
}

void parser(Mat matA, Mat matB, int N, int M, int O) { 
	int i, j;
	scanOneMatrix(matA, N, M);
	scanOneMatrix(matB, M, O);	
}


int mulMatrix(Mat mat1, Mat mat2, Mat matRes, int N_Off, int M_Off, int O_Off, int nSize, int mSize, int oSize) {
// TODO: not sure what we said, but I think matRes is suppose to be m*o, no offset.
// TODO: is matRes inited to zero? make sure it is when allocating. 
	int i, j, k;
	for (i=N_Off ; i < (N_Off + nSize) ; ++i) {
		for (k=O_Off ; k < (O_Off + oSize) ; ++k) {
			for (j=M_Off ; j < (M_Off + mSize) ; ++j) {
				matRes[i-N_Off][k-O_Off]+=(mat1[i][j])*(mat2[j][k]); 	
			}
		}
	}
}

void printMatrix (Mat mat, int rows, int cols) {
	int i, j;
	for (i=0 ; i < rows ; ++i) {
		for (j=0 ; j < cols ; ++j) {
			printf("%d", (mat[i][j]));
			if (j < (cols-1)) {
				printf("\t");
			}
		}
		if (i < (rows-1)) {
			printf("\n");
		}
	}
}

void printOutput(Mat matA, Mat matB, Mat matC, int N, int M, int O) { 
	//TODO: is it OK that there is an extra tab at the end of each row and enter at the end?
	printf("Matrix A:\n");
	printMatrix(matA, N, M);
	printf("Matrix B:\n");
	printMatrix(matB, M, O);
	printf("Matrix C:\n");
	printMatrix(matC, N, O);
}


void threadRun (Queue jobQueue, Mat matA, Mat matB, Mat matC){
	while(1){
		pthread_mutex_lock(jobQueue.queueMutex);
		//from here on mutex is held (=locked)
		if (jobQueue.iterator == jobQueue.size){
			pthread_mutex_unlock(jobQueue.queueMutex);
			return; //TODO: validate it's OK not to use pthread_exit
		} else // =>not empty
		Job job = _dequeueJob(jobQueue); //TODO: implement
		pthread_mutex_unlock(jobQueue.queueMutex);
		//from here on mutex is released (=unlocked)
		
		doJob(job);
	}
	return ;
}



/*
* Allocate matrixes A  B and C
* inits C to zero and allocate locks array
*/
int _allocAll(Mat matA, Mat matB, Mat matC, int N, int M, int O, Queue jobQueue){
	matA = malloc(N*sizeof(int*));
	matB = malloc(M*sizeof(int*));
	matC = malloc(N*sizeof(int*));
	jobQueue = malloc(sizeof(Queue));
	//TODO: allocate job array inside of jobQueue.
	//TODO: initialize pthread_mutex's in lockArray

	if (!matA || !matB || !matC || !jobQueue){
		free(matA);
		free(matB);
		free(matC);
		free(jobQueue);
		return ALLOCATION_FAILURE;
	}

	int i=0;
	for(; i<N; ++i){
		matA[i] = malloc(M*sizeof(int));
		matC[i] = malloc(O*sizeof(int));
	}
	for(; i<M; ++i){
		matB[i] = malloc(O*sizeof(int));
	}

	//verify allocation
	for(; i<N; ++i){
		if (!matA[i] || !matC[i]){
			_freeAll(matA, matB, matC, N, M, O, jobQueue);
			return ALLOCATION_FAILURE;
		}
	}
	for(; i<M; ++i){
		if(!matB[i]){
			_freeAll(matA, matB, matC, N, M, O, jobQueue);
			return ALLOCATION_FAILURE;

		}
	}

	//Allocate locks array
	//TODO: complete


	return ALLOCATION_SUCCESSFUL;
}

void * _threadRunWrapper(void * threadRunWrapperArgs){
	//TODO: implement
}